//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Security.Permissions;
    using System.ServiceModel;
    using System.Windows;
    using System.Windows.Controls;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties;
    using Microsoft.Research.ScientificWorkflow.UIDesigner;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.Monitor;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.Unity;
    using Microsoft.Research.DataLayer;

    /// <summary>
    /// Interaction logic for MonitorWindow.xaml
    /// </summary>
    public sealed partial class TridentMonitor : Window, IRegionManager, IDisposable
    {
        #region Declarations

        /// <summary>
        /// Widows error code for "No connection could be made because the target machine actively refused it". 
        /// </summary>
        private const int CONNECTION_REFUSED = 10061;

        /// <summary>
        /// Widows error code for "Incorrect function". 
        /// </summary>
        private const int INCORRECT_FUNCTION = 1;

        /// <summary>
        /// This is used to access connection manager API.
        /// </summary>
        private ConnectionManager connMgr;

        /// <summary>
        /// This is used to access registry connection.
        /// </summary>
        private Connection registryConnection;

        /// <summary>
        /// This is used to access the UI Designer Class.
        /// </summary>
        private UIHelper uiHelper;

        /// <summary>
        /// Field to store the current job id.
        /// </summary>
        private Guid currentJobId = Guid.Empty;

        /// <summary>
        /// The datatypes present.
        /// </summary>
        private Dictionary<String, DataTypeModel> dataTypes = new Dictionary<string, DataTypeModel>();

        /// <summary>
        /// Unity Container.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// Module implementaion.
        /// </summary>
        private IModule module;

        /// <summary>
        /// Set to true when the job is terminated by the user, false otherwise
        /// </summary>
        private bool terminatedByUser;

        /// <summary>
        /// Holds true if the JobTermination MessageBox can be displayed.
        /// </summary>
        private bool promptForJobTermination = true;
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor.
        /// </summary>
        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
        public TridentMonitor()
        {
            InitializeComponent();

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            // Initialize command Line parameters.
            this.InitializeCommmandLineParameters();

            // Initialize the Registry.
            this.InitializeRegistry();

            // Initialize the Connection.
            this.InitializeConnection();

            // Initailize DataTypes.
            this.ReadDatatypes();

            // Initailize Ui Helper.
            this.InitializeUIHelper();

            // Initailize Monitor Control.
            this.InitializeMonitorControl();
        }

        /// <summary>
        /// Authenticates the user.
        /// </summary>
        private void AuthenticateUser()
        {
            TridentAuthentication authendicate = new TridentAuthentication(this.registryConnection);
            authendicate.AuthenticateUser();

            // If the user is guest, then dont allow him to access the application
            if (TridentAuthentication.LoggedUserRole == UserRole.Guest)
            {
                TridentErrorHandler.ThrowTridentCustomException(
                    TridentErrorConstants.ErrorNumber1000000072,
                    TridentAuthentication.INVALIDLOGINERROR);
            }
        }
        #endregion

        #region Event Handlers
        /// <summary>
        /// This is handled essentially for preventing app crashes and doing appropriate logging.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">UnhandledExceptionEventArgs.</param>
        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            TridentErrorHandler.HandleUIException((Exception)e.ExceptionObject);
        }

        /// <summary>
        /// Handler for teminate button click
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">EventAgrs.</param>
        private void OnTerminateWorkflow(object sender, EventArgs e)
        {
            try
            {
                MessageBoxResult result = TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                    ScientificWorkFlow.ConfirmTerminateMessage);

                if (result == MessageBoxResult.Yes)
                {
                    this.terminatedByUser = true;
                    this.uiHelper.AbortCurrentJob(this.currentJobId);
                    //this.container.Resolve<IMonitorService>().Stop();
                }
            }
            catch (Exception ex)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);
            }
        }

        /// <summary>
        /// Event Handler for Window Load.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">RoutedEventArgs.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                // Initialize Unity container.
                this.InitializeContainer();

                // Initialize modules.
                this.InitializeModules();

                // Load Worklfow.
                this.LoadWorkflowFromJobID(this.currentJobId);

                this.container.Resolve<IDataProductService>().StartDataProductService(this.currentJobId, this.registryConnection);
                this.container.Resolve<IMonitorService>().Start(this.currentJobId);

            }
            catch (CommunicationException ex)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(TridentUtilities.Properties.TridentMonitor.BlackboardServiceUnavailableError);// "Blackboard service is currently unavailable; you will not be able to monitor the workflow execution.\n  The monitor window will now be closed.");
                Logger.Write(ex.Message);
                Environment.Exit(CONNECTION_REFUSED);
            }
            catch (Exception ex)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);
                Logger.Write(ex.Message);
                Environment.Exit(INCORRECT_FUNCTION);
            }
        }

        /// <summary>
        /// Updates the job status in the status bar.
        /// </summary>
        /// <param name="sender">Background thread.</param>
        /// <param name="e">Job status.</param>
        private void OnUpdateJobStatus(object sender, JobStatusEventArgs e)
        {
            try
            {
                //// updating the status text for all the states
                string formatString = (e.Status == JobStatus.Completed || e.Status == JobStatus.Aborted) ? "{0} {1}." : "{0} {1}. . .";
                lblStatus.Text = string.Format(CultureInfo.CurrentUICulture, formatString, e.JobName, e.Status.ToString());

                if (e.Status == JobStatus.Waiting && e.ErrorMessage != null &&
                    e.ErrorMessage.StartsWith(ScientificWorkFlow.JobTerminatedByUserMessage, StringComparison.OrdinalIgnoreCase) && promptForJobTermination)
                {
                    promptForJobTermination = false;

                    MessageBoxResult confirmResult =
                        TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                        ScientificWorkFlow.JobRetryMessage);

                    if (confirmResult == MessageBoxResult.No)
                    {
                        MessageBoxResult finalConfirmResult =
                            TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(
                            ScientificWorkFlow.JobTerminateConfirmMessage);

                        if (finalConfirmResult == MessageBoxResult.Yes)
                        {
                            this.terminatedByUser = true;
                            this.uiHelper.AbortCurrentJob(this.currentJobId);

                            // Stop the Monitor service
                            this.container.Resolve<IMonitorService>().HaltService();

                            // Stop the Dataproduct service
                            this.container.Resolve<IDataProductService>().HaltService();

                            lblStatus.Text = e.ErrorMessage;

                            this.currentJobId = Guid.Empty;
                        }
                    }

                    promptForJobTermination = true;
                }

                //// need to retain the status text so moved first, otherwise EnablePlayButton() will clear the message
                if (e.Status == JobStatus.Completed || e.Status == JobStatus.Aborted)
                {
                    this.currentJobId = Guid.Empty;
                    this.MonitorComposerControl.DisableTerminateButton();
                }

                if (e.Status == JobStatus.StartPending)
                {
                    this.MonitorComposerControl.EnableTerminateButton();
                }

                if (e.Status == JobStatus.StopPending)
                {
                    this.MonitorComposerControl.DisableTerminateButton();
                }

                if (e.Status == JobStatus.Aborted)
                {
                    if (this.terminatedByUser)
                    {
                        lblStatus.Text = string.Format(CultureInfo.CurrentUICulture,
                            formatString,
                            e.JobName,
                            ScientificWorkFlow.JobTerminatedByUserMessage);
                    }
                    else
                    {
                        //// in the case of job termination by the execution service prompt the error and clear the status message
                        TridentMessageBox.ShowTridentErrorMessageBox(
                            string.Format(CultureInfo.CurrentUICulture,
                            ScientificWorkFlow.UIHelperProblemExecuting,
                            e.ErrorMessage));
                        lblStatus.Text = string.Empty;
                    }
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }
        }

        /// <summary>
        /// Event Handler for Window Closing.
        /// </summary>
        /// <param name="sender">Monitor Window.</param>
        /// <param name="e">Cancel eventArgs.</param>
        private void monitorWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (!e.Cancel && this.IsWorkflowJobRunning)
            {
                MessageBoxResult result = TridentMessageBox.ShowTridentConfirmationYesNoCancelMessageBox(
                    ScientificWorkFlow.CloseRunningWorkFlowConfirmation);
                if (result == MessageBoxResult.Yes)
                {
                    this.uiHelper.AbortCurrentJob(this.currentJobId);
                }
                else if (result == MessageBoxResult.No)
                {
                    //Do not abort the job
                }
                else if (result == MessageBoxResult.Cancel)
                {
                    e.Cancel = true;
                }
            }

            if (!e.Cancel)
            {
                // Stop the Monitor service
                try
                {
                    this.container.Resolve<IMonitorService>().HaltService();
                }
                catch (Exception)
                {
                }

                // Stop the Dataproduct service
                try
                {
                    this.container.Resolve<IDataProductService>().HaltService();
                }
                catch (Exception)
                {
                }
            }
        }
        #endregion

        #region Initializers

        /// <summary>
        /// Initializes the registry.
        /// </summary>
        private void InitializeRegistry()
        {
            try
            {
                // Initialize Connection.
                SR_Connection.Init();
                connMgr = ConnectionManager.Create(ConnectionManager.CancelBehavior.ThrowException);
                connMgr.UIStyle = ConnectionManagerBase.UIStyleType.Lite;
            }
            catch (ConnectionFailure connectionFailure)
            {
                TridentErrorHandler.HandleUIException(connectionFailure);
                TridentMessageBox.ShowTridentErrorMessageBox(connectionFailure.InnerException.Message);
                Environment.Exit(0);
            }
            catch (InvalidOperationException exception)
            {
                TridentErrorHandler.HandleUIException(exception);
                TridentMessageBox.ShowTridentErrorMessageBox(exception.Message);
                Environment.Exit(0);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// This function is used to initialize the command line 
        /// parameters from the application level properties.
        /// </summary>
        private void InitializeCommmandLineParameters()
        {
            // Get the Job ID. 
            if (Application.Current.Properties.Contains("CurrentJobID"))
            {
                this.currentJobId = new Guid(Application.Current.Properties["CurrentJobID"].ToString());
            }
            else
            {
                TridentMessageBox.ShowTridentErrorMessageBox("No job present to monitor.");
                Environment.Exit(0);
            }
        }

        /// <summary>
        ///  Initialize Ui Helper.
        /// </summary>
        private void InitializeUIHelper()
        {
            try
            {
                uiHelper = new UIHelper(registryConnection);
            }
            catch (TridentCustomException tridentException)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(this, tridentException.Message);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleUIException(exception);
                TridentMessageBox.ShowTridentErrorMessageBox(this, exception.Message);
            }
        }

        /// <summary>
        /// Initialize registry connection.
        /// 
        /// </summary>
        private void InitializeConnection()
        {
            bool isValid;

            BackendStorageException connFailure;
            if (connMgr.HasDefaultConnection && !connMgr.TestConnection(connMgr.DefaultConnection, out connFailure))
            {
                TridentMessageBox.ShowTridentErrorMessageBox(connFailure.InnerException.Message);
                isValid = false;
            }
            else
            {
                isValid = true;
            }

            do
            {
                try
                {
                    if (!isValid)
                    {
                        registryConnection = connMgr.PickConnection(ConnectionManager.ConnectionUI.AlwaysShowUI);
                    }
                    else
                    {
                        registryConnection = connMgr.PickConnection(ConnectionManager.ConnectionUI.PromptIfNeeded);
                    }

                    if (null != registryConnection)
                    {
                        if (!Helper.IsValidConnection(registryConnection))
                        {
                            TridentMessageBox.ShowTridentErrorMessageBox(
                                TridentCommonResourceManager.GetString("InvalidRegistryConnection"));
                            isValid = false;
                            continue;
                        }

                        registryConnection.AutoSave = false;
                        this.AuthenticateUser();
                    }

                    isValid = true;
                }
                catch (ConnectionFailure connectionFailure)
                {
                    TridentErrorHandler.HandleUIException(connectionFailure);
                    TridentMessageBox.ShowTridentErrorMessageBox(connectionFailure.InnerException.Message);
                    isValid = false;
                }
                catch (InvalidOperationException exception)
                {
                    TridentErrorHandler.HandleUIException(exception);
                    TridentMessageBox.ShowTridentErrorMessageBox(exception.Message);
                    isValid = false;
                }
                catch (TridentCustomException customExp)
                {
                    TridentErrorHandler.HandleTridentCustomExceptionInUI(customExp, false);
                    isValid = false;
                }
                catch (Exception exception)
                {
                    TridentErrorHandler.HandleKnownExceptionsInUI(exception, false);
                    isValid = false;
                }
            } while (!isValid);

            if (null == registryConnection)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(
                    TridentCommonResourceManager.GetString("RegistryNotSelected"));
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// Read the datatypes.
        /// </summary>
        private void ReadDatatypes()
        {
            // Get All the datatypes from Embeded XML.
            DataTypesComposer.ReadAllDataTypes(this.dataTypes);

            // Get all user defined Dataypes.
            UIHelper.ReadUserDataTypeXml(this.dataTypes);
        }

        /// <summary>
        /// Initialize the mintor control.
        /// </summary>
        private void InitializeMonitorControl()
        {
            // Init Monitor Control.
            this.MonitorComposerControl = new ComposerControl(
                this.registryConnection,
                this.uiHelper.WorkflowComposer,
                this.dataTypes,
                RenderMode.MonitorMode);

            this.MonitorComposerControl.OnTerminate += new EventHandler(OnTerminateWorkflow);
            this.MonitorComposerControl.Name = "monitorComposerControl1";

            // Register Monitor COntrol.
            this.RegisterName(this.MonitorComposerControl.Name, this.MonitorComposerControl);

            // Add the Monitor Control to main window.
            this.MonitorRegion.Children.Add(this.MonitorComposerControl);

            // Set the current composer control to he monitor control.
            this.uiHelper.CurrentComposerControl = this.MonitorComposerControl;
        }

        /// <summary>
        /// Initialize the container.
        /// </summary>
        private void InitializeContainer()
        {
            this.container = new UnityContainer();
            this.container.RegisterInstance<IUnityContainer>(this.container, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance<IRegionManager>(this, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance<ComposerControl>("Monitor", this.MonitorComposerControl, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance<UIHelper>("UiHelper", this.uiHelper, new ExternallyControlledLifetimeManager());
        }

        /// <summary>
        /// Initialize the modules.
        /// </summary>
        private void InitializeModules()
        {

            ExternalRegistryHelperProxy.Initialize(this.registryConnection);

            this.module = this.container.Resolve<MonitorModule>();
            this.module.Initialize();

            this.container.Resolve<IDataProductService>().UpdateStatus += OnUpdateJobStatus;

        }
        #endregion

        /// <summary>
        /// Load the Workflow into the monitor tab using jobID.
        /// </summary>
        /// <param name="jobID">Job ID.</param>
        private void LoadWorkflowFromJobID(Guid jobID)
        {
            // Get Job from the ID.
            Job currentJob = GetJob(jobID);

            // Set the window title.
            this.Title = currentJob.Name + " - " + ManagementStudioResourceManager.GetString("MonitorWindowTitle");

            // Get the workflow ID.
            Guid currentWorkflowId = ((IObject)(currentJob.ActivityInstances[0].Activity)).ID;

            // load Workflow.
            this.uiHelper.LoadWorkflowFromLeftPane(currentWorkflowId);

            this.uiHelper.ComposerWorkflowModel.SetWorkflowParameters(currentJob);

            this.uiHelper.MonitorWorkflowModel = this.uiHelper.ComposerWorkflowModel;
        }

        /// <summary>
        /// Get the Job.
        /// </summary>
        /// <param name="jobID">Job ID.</param>
        /// <returns>Job.</returns>
        private Job GetJob(Guid jobID)
        {
            Job currentJob = null;
            try
            {
                currentJob = Job.Load(jobID, this.registryConnection);
            }
            catch (RecordNotFoundException exp)
            {
                TridentErrorHandler.HandleUIException(exp);
                TridentMessageBox.ShowTridentErrorMessageBox(this, ManagementStudioResourceManager.GetString("JobNotFoundInRegistry"));

                Environment.Exit(INCORRECT_FUNCTION);
            }
            catch (Exception exp)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exp);

                Environment.Exit(INCORRECT_FUNCTION);
            }

            return currentJob;
        }

        #region Public Properties
        /// <summary>
        /// Gets or sets the monitor composer control.
        /// </summary>
        public ComposerControl MonitorComposerControl
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets whether the workflow job is running
        /// </summary>
        private bool IsWorkflowJobRunning
        {
            get { return (this.currentJobId != Guid.Empty) ? true : false; }
        }

        #endregion

        #region IRegionManager Members

        /// <summary>
        /// This function is used to add the view to the reion.
        /// </summary>
        /// <param name="view">View.</param>
        /// <param name="regionName">Region Name.</param>
        public void Add(object view, string regionName)
        {
            Grid region = this.FindName(regionName) as Grid;
            if (region != null)
            {
                region.Children.Add(view as UIElement);
            }
        }

        /// <summary>
        /// Sets the title.
        /// </summary>
        /// <param name="title">The title.</param>
        public void SetTitle(string title)
        {
            if (!string.IsNullOrEmpty(title) && !string.IsNullOrEmpty(title.Trim()))
            {
                this.Title = title.Trim();
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Dispose method.
        /// </summary>
        public void Dispose()
        {
            if (this.container != null)
            {
                this.container.Dispose();
                GC.SuppressFinalize(this);
            }
        }

        #endregion
    }
}
